# 导入requests库，用于发送HTTP请求到GitHub API
import requests
# 导入jwt库，用于生成JSON Web Token，GitHub App认证需要
import jwt
# 导入time模块，用于获取当前时间戳
import time
# 导入datetime和timedelta，用于处理日期和时间间隔
from datetime import datetime, timedelta
# 导入load_dotenv，用于从.env文件加载环境变量
from dotenv import load_dotenv
# 导入os模块，用于访问环境变量
import os

# 执行load_dotenv()函数，从.env文件加载环境变量到程序中
load_dotenv()

# 这个文件是GitDiagram项目中的GitHub服务类(GitHubService)，
# 主要负责与GitHub API进行交互，获取仓库信息。它实现了两种认证方式：
# GitHub App认证和个人访问令牌(PAT)认证。
# 主要功能包括：1) 检查仓库是否存在；
# 2) 获取仓库的默认分支；
# 3) 获取仓库的文件树结构(排除了静态文件和生成的代码)；
# 4) 获取仓库的README文件内容。
# 这些功能为GitDiagram的核心功能(包括图表生成和文档生成)提供了必要的GitHub仓库数据支持。
# 获取的数据被用于分析仓库结构，并由AI模型转换为可视化图表和详细技术文档。

class GitHubService:
    def __init__(self, pat: str | None = None):
        # 尝试首先使用GitHub App认证方式
        # 从环境变量获取GitHub App的客户端ID
        self.client_id = os.getenv("GITHUB_CLIENT_ID")
        # 从环境变量获取GitHub App的私钥
        self.private_key = os.getenv("GITHUB_PRIVATE_KEY")
        # 从环境变量获取GitHub App的安装ID
        self.installation_id = os.getenv("GITHUB_INSTALLATION_ID")

        # 使用提供的个人访问令牌(PAT)，如果没有则使用环境变量中的PAT
        self.github_token = pat or os.getenv("GITHUB_PAT")

        # 如果没有提供任何凭据（既没有GitHub App凭据也没有PAT），警告用户关于速率限制
        if (
            not all([self.client_id, self.private_key, self.installation_id])
            and not self.github_token
        ):
            print(
                "\033[93mWarning: No GitHub credentials provided. Using unauthenticated requests with rate limit of 60 requests/hour.\033[0m"
            )

        # 初始化访问令牌为None，将在需要时通过_get_installation_token方法获取
        self.access_token = None
        # 初始化令牌过期时间为None
        self.token_expires_at = None

    # autopep8: off - 禁用autopep8格式化，保持代码原样
    def _generate_jwt(self):
        # 获取当前时间戳（秒）
        now = int(time.time())
        # 创建JWT载荷
        payload = {
            "iat": now,  # issued at - 令牌的签发时间
            "exp": now + (10 * 60),  # 10 minutes - 令牌的过期时间（当前时间+10分钟）
            "iss": self.client_id,  # issuer - 令牌的签发者（GitHub App的客户端ID）
        }
        # 使用私钥对载荷进行签名，生成JWT令牌
        # type: ignore 注释是为了忽略类型检查器的警告
        return jwt.encode(payload, self.private_key, algorithm="RS256")  # type: ignore

    # autopep8: on - 重新启用autopep8格式化

    def _get_installation_token(self):
        # 如果已有访问令牌且未过期，直接返回现有令牌
        # type: ignore 注释是为了忽略类型检查器的警告（token_expires_at可能为None）
        if self.access_token and self.token_expires_at > datetime.now():  # type: ignore
            return self.access_token

        # 生成JWT令牌，用于获取安装访问令牌
        jwt_token = self._generate_jwt()
        # 向GitHub API发送POST请求，获取安装访问令牌
        response = requests.post(
            f"https://api.github.com/app/installations/{
                self.installation_id}/access_tokens",
            headers={
                "Authorization": f"Bearer {jwt_token}",  # 使用JWT令牌进行认证
                "Accept": "application/vnd.github+json",  # 指定接受的响应格式
            },
        )
        # 解析响应JSON数据
        data = response.json()
        # 从响应中提取访问令牌
        self.access_token = data["token"]
        # 设置令牌过期时间为当前时间加1小时
        self.token_expires_at = datetime.now() + timedelta(hours=1)
        # 返回获取到的访问令牌
        return self.access_token

    def _get_headers(self):
        # 如果没有可用的凭据（既没有GitHub App凭据也没有PAT），返回基本请求头
        # 这种情况下将使用未认证请求，受到每小时60次请求的限制
        if (
            not all([self.client_id, self.private_key, self.installation_id])
            and not self.github_token
        ):
            return {"Accept": "application/vnd.github+json"}

        # 如果有个人访问令牌(PAT)，优先使用PAT认证
        if self.github_token:
            return {
                "Authorization": f"token {self.github_token}",  # 使用PAT进行认证
                "Accept": "application/vnd.github+json",  # 指定接受的响应格式
            }

        # 如果没有PAT但有GitHub App凭据，使用App认证
        # 调用_get_installation_token获取安装访问令牌
        token = self._get_installation_token()
        return {
            "Authorization": f"Bearer {token}",  # 使用安装访问令牌进行认证
            "Accept": "application/vnd.github+json",  # 指定接受的响应格式
            "X-GitHub-Api-Version": "2022-11-28",  # 指定GitHub API版本
        }

    def _check_repository_exists(self, username, repo):
        """
        使用GitHub API检查仓库是否存在。
        
        Args:
            username (str): GitHub用户名或组织名
            repo (str): 仓库名
            
        Raises:
            ValueError: 如果仓库不存在
            Exception: 如果API请求失败
        """
        # 构建GitHub API URL，用于获取仓库信息
        api_url = f"https://api.github.com/repos/{username}/{repo}"
        # 发送GET请求，使用_get_headers方法获取适当的认证头
        response = requests.get(api_url, headers=self._get_headers())

        # 如果响应状态码为404，表示仓库不存在
        if response.status_code == 404:
            raise ValueError("Repository not found.")
        # 如果响应状态码不是200，表示请求失败
        elif response.status_code != 200:
            raise Exception(
                f"Failed to check repository: {response.status_code}, {response.json()}"
            )

    def get_default_branch(self, username, repo):
        """获取仓库的默认分支。
        
        Args:
            username (str): GitHub用户名或组织名
            repo (str): 仓库名
            
        Returns:
            str or None: 仓库的默认分支名称，如果请求失败则返回None
        """
        # 构建GitHub API URL，用于获取仓库信息
        api_url = f"https://api.github.com/repos/{username}/{repo}"
        # 发送GET请求，使用_get_headers方法获取适当的认证头
        response = requests.get(api_url, headers=self._get_headers())

        # 如果响应成功，从响应JSON中提取默认分支名称
        if response.status_code == 200:
            return response.json().get("default_branch")
        # 如果请求失败，返回None
        return None

    def get_github_file_paths_as_list(self, username, repo, max_files=800):
        """
        获取开源GitHub仓库的文件树，排除静态文件和生成的代码。

        Args:
            username (str): GitHub用户名或组织名
            repo (str): 仓库名
            max_files (int): 最大文件数限制，默认800个文件

        Returns:
            str: 仓库中经过过滤的文件路径列表，每行一个路径。
            
        Raises:
            ValueError: 如果无法获取仓库文件树（仓库不存在、为空或私有）
        """

        def should_include_file(path):
            # 定义要排除的文件模式
            excluded_patterns = [
                # 依赖目录
                "node_modules/",  # Node.js依赖
                "vendor/",        # PHP/Ruby等依赖
                "venv/",          # Python虚拟环境
                ".venv/",         # 另一种Python虚拟环境
                "env/",           # 简化的Python环境
                "__pycache__/",   # Python缓存目录
                ".git/",          # Git目录
                "dist/",          # 分发目录
                "build/",         # 构建目录
                # 编译后的文件
                ".min.",          # 压缩的JS/CSS文件
                ".pyc",           # Python编译文件
                ".pyo",           # Python优化编译文件
                ".pyd",           # Python动态链接库
                ".so",            # 共享对象文件
                ".dll",           # 动态链接库
                ".class",         # Java编译文件
                # 资源文件
                ".jpg",           # 图片文件
                ".jpeg",          # 图片文件
                ".png",           # 图片文件
                ".gif",           # 图片文件
                ".ico",           # 图标文件
                ".svg",           # 矢量图形文件
                ".ttf",           # 字体文件
                ".woff",          # Web字体文件
                ".webp",          # WebP图片文件
                # 缓存和临时文件
                ".cache/",        # 缓存目录
                ".tmp/",          # 临时目录
                # 锁文件和日志
                "yarn.lock",      # Yarn锁文件
                "poetry.lock",    # Poetry锁文件
                "*.log",           # 日志文件
                # 配置文件
                ".vscode/",       # VS Code配置目录
                ".idea/",         # IntelliJ IDEA配置目录
            ]
            
            # 检查文件路径是否含有排除模式
            for pattern in excluded_patterns:
                if pattern in path.lower():
                    return False
            return True

        try:
            # 检查仓库是否存在
            self._check_repository_exists(username, repo)
            
            # 获取默认分支
            default_branch = self.get_default_branch(username, repo) or "main"
            
            # 获取仓库的文件树
            api_url = f"https://api.github.com/repos/{username}/{repo}/git/trees/{default_branch}?recursive=1"
            response = requests.get(api_url, headers=self._get_headers())
            
            if response.status_code != 200:
                raise ValueError(f"Failed to get repository file tree: {response.status_code}, {response.json().get('message', '')}")
            
            data = response.json()
            
            # 检查请求是否被截断（超大型仓库GitHub API会限制）
            if data.get("truncated", False):
                print(f"Warning: Repository tree is truncated due to size limits ({username}/{repo})")
            
            # 过滤并限制文件数量
            file_list = []
            for item in data.get("tree", []):
                if item["type"] == "blob" and should_include_file(item["path"]):
                    file_list.append(item["path"])
                    # 达到最大文件数限制时停止
                    if len(file_list) >= max_files:
                        print(f"Warning: Repository exceeds max file limit, truncating to {max_files} files")
                        file_list.append(f"... and more (repository truncated at {max_files} files)")
                        break
            
            # 将文件列表排序并格式化为字符串
            file_list.sort()
            return "\n".join(file_list)
            
        except Exception as e:
            # 如果出现任何异常，抛出ValueError
            raise ValueError(f"Failed to get repository file tree: {str(e)}")

    def get_github_readme(self, username, repo):
        """
        获取开源GitHub仓库的README内容。

        Args:
            username (str): GitHub用户名或组织名
            repo (str): 仓库名

        Returns:
            str: README文件的内容，如果不存在README则返回空字符串。
        """
        # 首先检查仓库是否存在
        self._check_repository_exists(username, repo)

        # 然后尝试获取README
        # 使用专门的README API端点
        api_url = f"https://api.github.com/repos/{username}/{repo}/readme"
        # 发送GET请求，使用_get_headers方法获取适当的认证头
        response = requests.get(api_url, headers=self._get_headers())

        # 如果响应状态码为404，表示没有找到README，返回空字符串而不是抛出异常
        if response.status_code == 404:
            print(f"Warning: No README found for repository {username}/{repo}. Proceeding without README.")
            return ""
        # 如果响应状态码不是200，表示请求失败
        elif response.status_code != 200:
            raise Exception(
                f"Failed to fetch README: {
                            response.status_code}, {response.json()}"
            )

        # 解析响应JSON数据
        data = response.json()
        # 使用download_url直接获取README的原始内容
        readme_content = requests.get(data["download_url"]).text
        # 返回README内容
        return readme_content
